package org.yaac.client.ui;

import static com.google.common.base.Strings.isNullOrEmpty;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.yaac.client.YaacCallback;
import org.yaac.client.activity.BrowserActivity;
import org.yaac.client.util.ExceptionManager;
import org.yaac.shared.crud.MetaNamespace;
import org.yaac.shared.editor.EntityInfo;
import org.yaac.shared.util.StringUtils;


import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.Range;

/**
 * @author Max Zhu (thebbsky@gmail.com)
 * 
 */
public class BrowserViewImpl extends Composite implements BrowserView {

	private static BrowserViewImplUiBinder uiBinder = GWT.create(BrowserViewImplUiBinder.class);

	@UiTemplate("BrowserView.ui.xml")
	interface BrowserViewImplUiBinder extends UiBinder<Widget, BrowserViewImpl> {
	}
	
	@UiField
	ListBox namespaceListBox;
	
	@UiField
	ListBox kindListBox;
	
	@UiField
	TextBox filtersTxt;
	
	// internal status for filters text
	private boolean waitingForFirstClick;
	
	@UiField(provided=true)
	SimplePager pager;
	
	@UiField(provided=true)
	CellTable<EntityInfo> entityTable;
	
	private BrowserActivity listener;
	
	private List<MetaNamespace> namespaces;
	
	// make sure metadata will only be loaded once by activity
	private boolean isMetaDataInit;
	
	private final ExceptionManager exceptionManager;
	
	@Inject
	BrowserViewImpl(ExceptionManager exceptionManager) {
		this.exceptionManager = exceptionManager;
		
		// init entity table
		entityTable = new CellTable<EntityInfo>();
		entityTable.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);
	    
		// Create a Pager to control the table.
	    SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
	    pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
	    pager.setDisplay(entityTable);
	    
		initWidget(uiBinder.createAndBindUi(this));
		
		// disable them at the begining
		namespaceListBox.setEnabled(false);
		kindListBox.setEnabled(false);
		filtersTxt.setEnabled(false);
		this.waitingForFirstClick = true;
		
		this.isMetaDataInit = false;
	}

	@UiHandler("filtersTxt")
	void onFiltersTxtClick(ClickEvent event) {
		if (waitingForFirstClick) {
			filtersTxt.setText("");
			waitingForFirstClick = false;
		}
	}
	
	@UiHandler("filtersTxt")
	void onFiltersTxtKeyPress(KeyPressEvent event) {
		char key = event.getCharCode();
		if (KeyCodes.KEY_ENTER == key) {
			refreshDataGrid();
		}
	}
	
	@Override
	public void setPresenter(BrowserActivity listener) {
		this.listener = listener;
	}
	
	@Override
	public void onMetadataReady(List<MetaNamespace> namespaces) {
		this.isMetaDataInit = true;
		
		this.namespaces = namespaces;
		
		this.namespaceListBox.setEnabled(true);
		this.kindListBox.setEnabled(true);
		this.filtersTxt.setEnabled(true);
		
		this.namespaceListBox.clear();
		for (MetaNamespace namespace : namespaces) {
			this.namespaceListBox.addItem(namespace.getNamespaceName());	
		}
		
		this.refreshKindListBox();
	}
	
	private void refreshKindListBox() {
		String selectedNamespace = namespaceListBox.getValue(namespaceListBox.getSelectedIndex());
		for (MetaNamespace namespace : namespaces) {
			// we want to make null == empty string here
			// reason it list box will convert null to emtpy string automatically
			if ((isNullOrEmpty(namespace.getNamespaceName()) && isNullOrEmpty(selectedNamespace))
					|| namespace.getNamespaceName().equals(selectedNamespace)) {
				this.kindListBox.clear();
				for (String kindName : namespace.getKindsMap().keySet())  {
					this.kindListBox.addItem(kindName);
				}
				
				break;
			}
		}
		
		this.refreshDataGrid();
	}
	
	private void refreshDataGrid() {
		String selectedKindName = kindListBox.getValue(kindListBox.getSelectedIndex());
		String filters = waitingForFirstClick ? null : filtersTxt.getText().trim();
		this.updateEntityTable(selectedKindName, filters);
	}

	@UiHandler("namespaceListBox")
	void onNamespaceListBoxChange(ChangeEvent event) {
		this.refreshKindListBox();
	}
	
	@UiHandler("kindListBox")
	void onKindListBoxChange(ChangeEvent event) {
		this.filtersTxt.setText("");	// clear filters
		this.refreshDataGrid();
	}
	
	private BrowserDataProvider currDataProvider;
	
	/**
	 * no need to reload when kind is not changed
	 */
	private String currKind;
	
	private String currFilter;
	
	/**
	 * current column map, to make sure same column will not be duplicated
	 */
	private Map<String, Column<EntityInfo, SafeHtml>> currColMap;
	
	private void updateEntityTable(String kind, String filter) {		
		if (!StringUtils.sameStr(kind, currKind) || !StringUtils.sameStr(filter, currFilter)) {
			currKind = kind;
			currFilter = filter;
			currColMap = new HashMap<String, Column<EntityInfo, SafeHtml>>();
			
			// step 1 : prepare tables based on selected properties
			// remove all existing columns
			while (entityTable.getColumnCount() > 0) {
				entityTable.removeColumn(0);			
			}
			
			// only key now, all columns are dynamic
			entityTable.addColumn(new Column<EntityInfo, SafeHtml>(new SafeHtmlCell()) {
				@Override
				public SafeHtml getValue(EntityInfo entity) {
					SafeHtmlBuilder sb = new SafeHtmlBuilder();
					sb.appendHtmlConstant(entity.getKey().asHtml());
					return sb.toSafeHtml();
				}
			}, "ID/Name");	// TODO : i18n
			
			// reset to first page
			pager.firstPage();
			
			// step 2 : load table
			// replace current data provider with new one
			if (currDataProvider != null) {				
				entityTable.setRowCount(0, false);
				currDataProvider.removeDataDisplay(entityTable);
			}
			
			currDataProvider = new BrowserDataProvider(currKind, currFilter);
			currDataProvider.addDataDisplay(entityTable);
		}
	}
	
	/**
	 * check all entities, make sure no column(s) is missing
	 * 
	 * @param vals
	 */
	private void ensureNewColumns(List<EntityInfo> vals) {
		for (EntityInfo entity : vals) {
			for (final String propertyName : entity.getPropertisMap().keySet()) {
				if (!currColMap.containsKey(propertyName)) {
					Column<EntityInfo, SafeHtml> col = new Column<EntityInfo, SafeHtml>(new SafeHtmlCell()) {
						@Override
						public SafeHtml getValue(EntityInfo entity) {
							String html = entity.propertyAsHtml(propertyName);
							SafeHtmlBuilder sb = new SafeHtmlBuilder();
							sb.appendHtmlConstant(html);
							return sb.toSafeHtml();
						}
					};
					
					entityTable.addColumn(col, propertyName);
					currColMap.put(propertyName, col);
				}
			}
		}
	}
	
	/**
	 * @author Max Zhu (thebbsky@gmail.com)
	 *
	 */
	private class BrowserDataProvider extends AsyncDataProvider<EntityInfo> {
		
		private final String kindName;
		
		private final String filter;
		
		private final List<EntityInfo> cache;
		
		BrowserDataProvider(String kindName, String filter) {
			super();
			this.kindName = kindName;
			this.filter = filter;
			
			// it's less likely that user will browse more than 1000 records
			this.cache = new ArrayList<EntityInfo>(1000);
		}

		@Override
		protected void onRangeChanged(HasData<EntityInfo> display) {
			Range range = display.getVisibleRange();
			final int start = range.getStart();
			final int length = range.getLength();
			
			if (start + length < cache.size()) {
				List<EntityInfo> vals = cache.subList(start, start + length);
				updateRowData(start, vals);
			} else {
				listener.loadEntities(kindName, filter, start, length, 
						new YaacCallback<List<EntityInfo>>() {

					@Override
					public void onSuccess(List<EntityInfo> vals) {
						if (vals.isEmpty()) {
							Window.alert("No result returned");
						}
						
						cache.addAll(start, vals);
						ensureNewColumns(vals);
						updateRowData(start, vals);
					}

					@Override
					public void onFailure(Throwable caught) {
						exceptionManager.handle(caught);
					}
				});	
			}
		}
	}

	@Override
	public boolean isMetaDataInit() {
		return this.isMetaDataInit;
	}
}
